package satellite

import (
	"errors"
	"io"
	"time"
)

var (
	SuperClose     = errors.New("close")
	SuperErrorRecv = errors.New("error recv")
	SuperEmptyRecv = errors.New("empty recv")
)

type Super struct {
	*Socket

	protocol Protocol

	recv chan []byte

	dialList  []Dial
	dialNum   int
	dialIndex int

	resend time.Duration
}

func NewSuper() *Super {
	s := Super{
		Socket:   NewSocket(),
		protocol: NewVariProtocol(0),

		recv: make(chan []byte),

		dialList:  []Dial{},
		dialNum:   0,
		dialIndex: -1,

		resend: time.Second,
	}
	return &s
}

func (super *Super) Listen() error {
	recv := func(conn Conn) {
		for {
			if super.closed {
				return
			}
			if !conn.Connected() {
				return
			}
			packet, err := super.protocol.Read(conn)
			if err != nil {
				return
			}
			//fmt.Println("listen read", conn.RemoteAddr())
			b := packet.ReadBytes()
			if err = packet.Close(); err != nil {
				return
			}
			select {
			case super.recv <- b:
			case <-super.close:
				return
			}
		}
	}
	return super.Socket.Listen(func(accept <-chan Conn) {
		go func() {
			for {
				select {
				case conn := <-accept:
					go recv(conn)
				case <-super.close:
					return
				}
			}
		}()
	})
}

func (super *Super) Dial() error {
	return super.Socket.Dial(func(conn Dial) {
		super.dialList = append(super.dialList, conn)
		super.dialNum = len(super.dialList)
		conn.Hook(super)
	})
}

func (super *Super) removeDial(index int) {
	l := len(super.dialList)
	if l == 1 {
		super.dialList = []Dial{}
	} else {
		if index == 0 {
			super.dialList = super.dialList[1:]
		} else if index == l-1 {
			super.dialList = super.dialList[0:index]
		} else {
			super.dialList = append(super.dialList[:index], super.dialList[index+1:]...)
		}
	}
	super.dialNum = len(super.dialList)
}

func (super *Super) Close() error {
	if super.closed {
		return nil
	}
	super.dialList = super.dialList[:0]
	super.dialIndex = -1
	super.dialNum = 0
	return super.Socket.Close()
}

func (super *Super) Send(b []byte) error {
	if super.closed {
		return SuperClose
	}
	if len(super.dialList) == 0 {
		return SuperClose
	}
	//fmt.Println("send", b)
	for {
		// 轮训
		if super.dialIndex+1 < super.dialNum {
			super.dialIndex += 1
		} else {
			super.dialIndex = 0
		}
		index := super.dialIndex
		conn := super.dialList[index]
		_, err := super.protocol.Write(nil, conn, func(writer io.Writer) error {
			_, err := writer.Write(b)
			return err
		})
		//fmt.Println("send", number, err)
		if err == nil {
			return nil
		}
		// 如果连接内部重试关闭了，则移除该连接
		//fmt.Println("send error", err)
		if !conn.Opened() {
			goto Disconnect
		} else {
			conn.Error(err)
		}
		//fmt.Println("wait reconnect", err, time.Now())
		select {
		case <-super.close:
			return SuperClose
		case <-conn.Reconnect():
			//fmt.Println("resend", err, time.Now())
		}
		if conn.Opened() {
			//fmt.Println("resend connect", time.Now())
			continue
		}
	Disconnect:
		super.removeDial(index)
		super.dialIndex -= 1
		// 如果内部连接都为空了，返回关闭错误
		if len(super.dialList) == 0 {
			return SuperClose
		}
		if super.closed {
			return SuperClose
		}
	}
}

func (super *Super) Recv() ([]byte, error) {
	if !super.listened {
		return nil, SuperClose
	}
	select {
	case b := <-super.recv:
		//fmt.Println("recv", b)
		if b == nil {
			return nil, SuperErrorRecv
		}
		if len(b) == 0 {
			return nil, SuperEmptyRecv
		}
		return b, nil
	case <-super.close:
		return nil, SuperClose
	}
}

func (super *Super) OnConnect(conn Dial) {
	go func() {
		var b []byte
		for {
			if super.closed {
				return
			}
			if conn == nil {
				return
			}
			if !conn.Opened() {
				return
			}
			packet, err := super.protocol.Read(conn)
			if err != nil {
				conn.Error(err)
				return
			}
			//fmt.Println("dial read", conn.RemoteAddr())
			b = packet.ReadBytes()
			if err := packet.Close(); err != nil {
				conn.Error(err)
				return
			}
			select {
			case super.recv <- b:
				continue
			case <-super.close:
				return
			}
		}
	}()
}

func (super *Super) OnDisconnect(conn Dial) {

}

func (super *Super) OnError(conn Dial, err error) {
	if super.Logger != nil {
		super.Error("super:", err)
	}
}
